using System;
using System.Text;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using TDS = Tridion.ContentManager.Interop.TDS;


namespace Tridion.Gateway
{
    [Serializable]
    public abstract class Facade
    {
        protected Facade(Type type, Object value, bool shallowCopy)
        {
            if (value != null)
            {
                PropertyInfo[] properties = type.GetProperties();
                FieldInfo[] fields = this.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
                foreach (FieldInfo field in fields)
                {
                    object[] attributes = field.GetCustomAttributes(true);
                    PropertyInfo property = Array.Find<PropertyInfo>(properties, new Predicate<PropertyInfo>(delegate(PropertyInfo p)
                    {
                        return p.Name.ToLower() == field.Name.ToLower();
                    }));

                    if (property != null)
                    {
                        if (Array.Exists<object>(attributes, new Predicate<object>(delegate(object o)
                        {
                            return o.GetType().Name == typeof(IgnoreReflectedFieldAttribute).Name;

                        })))
                        {
                            try
                            {
                                //object o = property.GetValue(value, null);
                                //if (o != null)
                                //{
                                    Initialize(field, property, value); 
                                    //Initialize(field, o);
                               // }
                            }
                            catch
                            { 
                            
                            }
                        }
                        else
                        {
                            switch (Type.GetTypeCode(property.PropertyType))
                            { 
                                case TypeCode.Object:
                                    if (shallowCopy) continue;
                                    Type[] types = property.PropertyType.FindInterfaces(new TypeFilter(delegate(Type typ, object obj)
                                        {
                                           return typ.Name == "IEnumerable";
                                        }), null);

                                    if (types.Length == 1) //its an array
                                    {
                                        Type fieldType = Type.GetType(field.FieldType.AssemblyQualifiedName.Replace("[]", ""));
                                        Type propertyType = Type.GetType(property.PropertyType.AssemblyQualifiedName.Replace(property.PropertyType.Name,  property.PropertyType.Name.Remove(property.PropertyType.Name.Length - 1, 1)));
                                        System.Collections.IEnumerable e = property.GetValue(value, null) as System.Collections.IEnumerable;
                                        if (e != null)
                                        {
                                            List<object> l = new List<object>();
                                            System.Collections.IEnumerator r = e.GetEnumerator();
                                            ConstructorInfo ctor = Facade.GetConstructor(fieldType, propertyType);
                                            while (r.MoveNext())
                                            {
                                                l.Add(r.Current);
                                            }
                                            Array array = Array.CreateInstance(fieldType, l.Count);
                                            for(int i = 0; i < array.Length; i++)
                                            {
                                                array.SetValue(ctor.Invoke(new object[] {l[i],true}),i);
                                            }
                                            field.SetValue(this, array);
                                        }
                                    }
                                    else
                                    {
                                        ConstructorInfo ctor = Facade.GetConstructor(field.FieldType, property.PropertyType); 
                                        if (ctor != null)
                                        {
                                            object o = ctor.Invoke(new object[] { property.GetValue(value, null),true });
                                            field.SetValue(this, o);
                                        }
                                    }
                                    break;
                                default:
                                        field.SetValue(this, property.GetValue(value, null));
                                    break;
                            }
 
                            
                        }
                    }

                }
            }
            Facade.ReleaseComObject(value);
       }

        protected abstract void Initialize(FieldInfo field, PropertyInfo property, Object value);

        private static ConstructorInfo GetConstructor(Type containerType, Type objectType)
        {
            return containerType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null, new Type[] { objectType, typeof(bool) }, null);
        
        }

        internal static Type GetType(object o)
        {
            List<Type> interfaceList = new List<Type>();
            Assembly a = Assembly.Load(typeof(TDS.PublicationClass).Assembly.FullName);
            Type[] types = a.GetExportedTypes();
            Array.ForEach<Type>(a.GetExportedTypes(), new Action<Type>(delegate(Type t)
            {
                if (t.IsInterface)
                {
                    if (t.Name.Substring(0, 1) != "_")
                    {
                        interfaceList.Add(t);
                    }
                }
            }));

            IntPtr i;
            IntPtr u = System.Runtime.InteropServices.Marshal.GetIUnknownForObject(o);

            foreach (Type type in interfaceList)
            {
                Guid iid = System.Runtime.InteropServices.Marshal.GenerateGuidForType(type);
                System.Runtime.InteropServices.Marshal.QueryInterface(u, ref iid, out i);
                if (i != IntPtr.Zero)
                {
                    System.Runtime.InteropServices.Marshal.Release(i);
                    System.Runtime.InteropServices.Marshal.Release(u);
                    return type;

                }
            }

            System.Runtime.InteropServices.Marshal.Release(u);
            return null;
        }

        public static object Create(object o, bool shallowCopy)
        {
            Type type = Facade.GetType(o);
            if (type != null)
            {
                ConstructorInfo ctor = Facade.GetConstructor(Type.GetType(String.Format("Tridion.Gateway.{0}, Tridion.Gateway", type.Name)), type);
                if (ctor != null)
                {
                    object obj = ctor.Invoke(new object[] { o, shallowCopy });
                    if (obj != null)
                    {
                        return obj;
                    }

                }
            }
            throw new InvalidCastException(o.GetType().Name);
        }

        public static void ReleaseComObject(object o)
        {
            if (o != null)
                Marshal.ReleaseComObject(o);
        }
    }
}
